package grupo5;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;


public class Interpreter {

	ArrayList<Instruction> dirty; // Arraylist de las instrucciones
	Instruction[] itsbinarybaby; // Arreglo listo para imprimir
	Map<String, Integer> posvariables = new HashMap<String,Integer>(); // Diccionario de vars y sus posiciones

	public Interpreter(){
		this.dirty = new ArrayList<Instruction>();
		
		String primeraIns = "SETSP";
		Instruction instruccion = new Instruction(primeraIns, false, "", false, false,false);
		dirty.add(instruccion);
		
		String decrementoSP = "SETSP";
		instruccion = new Instruction(decrementoSP, false, "", false, false,false);
		dirty.add(instruccion);
		
	}

	public Instruction[] getInstructions(){
		return this.itsbinarybaby;
	}
	
	
	/**
	 * Interpreta las instrucciones limpias desde un ArrayList
	 * @param program ArrayList con las instrucciones
	 */
	public void interpretCleanInput(ArrayList<String> program){

		// Interpretaremos linea a linea el programa
		Iterator<String> i = program.iterator();

		boolean codeStarted = false; // Nos fijamos si estamos en DATA: o CODE:
		boolean keepValues = false; // Mantenemos los valores desde el ciclo anterior
		boolean isLabel = false; // Si es label
		boolean isVarNum = false; // Si es variable continuacion de un vector
		boolean isVar = false; // Si es variable
		boolean isPOP = false; // Si es POP
		boolean isCALL = false; //
		boolean isRET = false; //
		String varID = ""; // ID de la variable
		String labelID = ""; // ID de la label
		String varValue = ""; // Valor de la variable
		String insFoo = ""; // instrucciones hardcodeadas

		while(i.hasNext()){
			while(true) {
				if(!keepValues){ // Solo reseatemos despues de asignar label a una linea
					isLabel = false;
					isVarNum = false;
					isVar = false;
					isPOP = false;
					isCALL = false;
					isRET = false;
					varID = "";
					labelID = "";
					varValue ="";
					insFoo= "";
				}
				String ins = i.next();
				if(!codeStarted){// Aun no llegamos a CODE:
					if(ins.startsWith("DATA:")){ // Si es la linea DATA: volvemos a leer (no es una linea de instruccion real)
						break;
					}
					else if(ins.length()==1){ // Es una variable contuacion de un vector
						isVarNum = true;
						varValue= ins;
					} else if(ins.contains(" ")) { // Es una variable, guardamos su ID y valor
						isVar = true;
						varID = ins.split(" ")[0];
						varValue = ins.split(" ")[1];
					}else if(ins.startsWith("CODE:")){ // Llegamos a CODE: volvemos a leer.
						codeStarted= true;
						break;
					}
				}
				else if(ins.contains(":")){ // Es una label, asignamos sus valores, los mantenemos y volvemos a empezar para asignarlos a proxima linea
					isLabel = true;
					labelID = ins.substring(0,ins.indexOf(':'));
					keepValues = true;
					break;
				}
				else if(ins.startsWith("JMP") || ins.startsWith("JEQ") || ins.startsWith("JNE") ){
					//FUN FUN FUN
				} else if(ins.startsWith("POP")){
					isPOP = true;
				}else if(ins.startsWith("CALL")){
					isCALL = true;
				}else if(ins.startsWith("RET")){
					isRET = true;
				}

				// Creamos la nueva instruccion
				// La agregamos al arraylist de instrucciones

				Instruction parsedIns;
				
				if(isVar || isVarNum){ // Si es variable necesita dos instrucciones: MOV A, valor + MOV (DIR),A
					// MOV A, valor
					insFoo = "MOV A,"+varValue;
					parsedIns = new Instruction(insFoo, isLabel, labelID, false, false,false);
					dirty.add(parsedIns);
					
					// Da lo mismo ins porque en Instruction se hardcodea MOV (DIR), A y se guarda DIR en varPos
					insFoo = "MOV (DIR),"+varValue;//Guardamos la variable memoria
					parsedIns = new Instruction(insFoo, isLabel, labelID, isVar, isVarNum,false);
					dirty.add(parsedIns);
					
				}else if(isPOP){ // Si es POP
					
					insFoo = "SP++";
					parsedIns = new Instruction(insFoo, isLabel, labelID, false, false,false);
					dirty.add(parsedIns);
					
					parsedIns = new Instruction(ins, isLabel, labelID, false, false,false);
					dirty.add(parsedIns);
				}else if(isCALL){
					//Guardamos A en un espacio de memoria
					parsedIns = new Instruction("", isLabel, labelID, false, true, false);
					dirty.add(parsedIns);
					
					insFoo = "Mem[SP]=PC";
					parsedIns = new Instruction(insFoo, isLabel, labelID, false, false,false);
					dirty.add(parsedIns);
					
					insFoo = "MOV A,Mem[SP]";
					parsedIns = new Instruction(insFoo, isLabel, labelID, false, false,false);
					dirty.add(parsedIns);
					
					insFoo = "ADD A,6";
					parsedIns = new Instruction(insFoo, isLabel, labelID, false, false,false);
					dirty.add(parsedIns);
					
					insFoo = "MOV Mem[SP],A";
					parsedIns = new Instruction(insFoo, isLabel, labelID, false, false,false);
					dirty.add(parsedIns);
					
					//Recuperamos A del espacio de memoria guardado
					parsedIns = new Instruction("", isLabel, labelID, false, false, true);
					dirty.add(parsedIns);
					
					//ins es "CALL ins"
					parsedIns = new Instruction(ins, isLabel, labelID, false, false,false);
					dirty.add(parsedIns);
					
				}else if(isRET){
					
					insFoo = "SP++";
					parsedIns = new Instruction(insFoo, isLabel, labelID, false, false,false);
					dirty.add(parsedIns);
					
					parsedIns = new Instruction(ins, isLabel, labelID, false, false,false);
					dirty.add(parsedIns);
	
				}else{ // Si no es una instruccion normal
					parsedIns = new Instruction(ins, isLabel, labelID, isVar, isVarNum,false);
					dirty.add(parsedIns);
				}
				
				// Agregamos la variable al diccionario para despues recuperar su posicion en la RAM
				if(isVar){
					posvariables.put(varID, parsedIns.getVarPos());
				}
				//Reiniciamos los valores y seguimos con el ciclo original.
				keepValues = false;
				break;
			}
		}

	}

	/**
	 * Remmplaza labels por si posicion en la ROM
	 * Reemplaza vars por su direccion en la RAM
	 */
	public void unLabelunVar(){
		for(Instruction i : dirty){
			if(i.isJump){
				for(Instruction j: dirty){
					if(j.isLabel){
						if(i.labelTO.equals(j.labelID)){
							i.literal = ""+ dirty.indexOf(j);
						}
					}
				}

			}
		}

		for(Instruction i : dirty){
			for(Map.Entry<String,Integer> variable : posvariables.entrySet()){
				if(i.literal.equals(variable.getKey())){
					i.literal = ""+variable.getValue();
				}

			}
		}


	}

	/**
	 * Reemplaza los literales a binarios de 8bits
	 */
	public void literalsToBinary(){

		for(Instruction i: dirty) {
			i.literal = toBinary8bits(i.literal);
		}

	}

	/**
	 * Reemplaza un string de numero a binario de 8 bit.
	 * @param literal puede ser en decimal(10, 10d), binario (0101b) o hexadecimal (0Eh)
	 * @return String de binario en 8 bit.
	 */
	private String toBinary8bits(String literal) {
		if(literal.length()>0){
			literal = literal.trim();
			if(literal.endsWith("h")){
				literal = literal.substring(0,literal.length()-1);
				int num = Integer.parseInt(literal, 16);
				literal = Integer.toBinaryString(num);

			}
			else if(literal.endsWith("b")){
				literal = literal.substring(0,literal.length()-1);
			}
			else{
				if(literal.endsWith("d")){
					literal = literal.substring(0,literal.length()-1);
				}
				int num = Integer.parseInt(literal);
				literal = Integer.toBinaryString(num);
			}
		}
		int largo = literal.length();
		for(int i = 0; i<8-largo;i++){
			literal = "0" + literal;
		}
		return literal;
	}
	
	/**
	 * Convierle la ArrayList a arreglo.
	 */
	public void toBinaryArray(){
		Object[] aux = this.dirty.toArray();
		itsbinarybaby = new Instruction[aux.length];
		for(int i = 0; i< aux.length; i++) {
			itsbinarybaby[i] = (Instruction) aux[i];
		}
	}

}
